package org.hsc.proxy;

import java.io.*;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An http request.
 * 
 * @author Alon Halfon
 */
public class HttpRequest {
	
	private final static Logger logger=LoggerFactory.getLogger(HttpRequest.class);
    /*
     * each key is header, the value is linked list of values
     */
    private HashMap<String,LinkedList<String>> headers;
    
    /** holds the method's request */
    private String method;
    
    /** holds the http version  */
    private String version;
    
    /** io will be passed after parsing ("contain" the response body)  */  
    private ByteArrayOutputStream requestBody;
    
    /*
     * the url....
     */
    private URL url; 
    
    /**The request line */
    private String requestLine;
    
    /*
     * c-tor getting stream and create HttpConnection
     */ 
    HttpRequest(InputStream io) throws IOException, EmptyHttpRequestException{
        this.parse(io);
    }
    
    /*
     * Construct empty httpRequest (later will be filled with parse function)
     */
    HttpRequest() 
    {
    }
    
    /*
     * get the total address:absolute  (Refer to the RFC for more information...)
     */
    public String getCompleteUrl(){
            return this.url.toString();

    }
    
    private String headersString=null;
    
    /*
     * get InputStream and start parsing the request
     */
    public void parse(InputStream in) throws IOException, EmptyHttpRequestException{
        synchronized( in ) {
            try {
                while( in.available() == 0 )
                    Thread.sleep(10);
            } catch (InterruptedException ie) {
            	logger.error(ie.getMessage(),ie);
            }

            StringBuffer h = new StringBuffer(1024);
            int b=0;
            boolean stop = false;
            int len=0;
            while( !stop && ((b=in.read()) > -1) ) {
                h.append((char)b);
                len=h.length();
                stop =  (h.charAt(len-1) == '\n' &&
                        h.charAt(len-2) == '\r' &&
                        h.charAt(len-3) == '\n' &&
                        h.charAt(len-4) == '\r');
            }
            //String headersString = h.toString();
            headersString = h.toString();
            if( headersString.length() == 0 )
                throw new EmptyHttpRequestException();
            
            logger.debug("request header:"+headersString);
            // Create new message-headers container
            headers=new LinkedHashMap<String,LinkedList<String>> ();
            String line=null;
            String[] lines = headersString.split("\r\n");
            String[] splitedLine, header;
            String key, value;

            for( int i=0; i<lines.length; ++i ){
                line=lines[i];
                if( i==0 ){ // Status Line
                    this.requestLine = line;
                    
                }else {
                    // Message-header = field-name ":" [ field-value ]			
                    header = line.split(":", 2);
                    key = header[0].trim();
                    value = header[1].trim();
                    //
                    if (!headers.containsKey(key)){
                        LinkedList<String> values=new LinkedList<String>();
                        values.add(value);
                        headers.put(key, values); 
                    }else{
                        LinkedList<String> values= headers.get(key);
                        values.add(value);
                    }
                }
            }

           
            //
            splitedLine = this.requestLine.split(" ", 3);
            //the status line is the first line
            method = splitedLine[0];
            try {
                this.url = new URL(splitedLine[1]);
            } catch (Exception e ) {
            	this.url = new URL("http://" + this.getHost() + splitedLine[1]);
            }
            this.version = splitedLine[2];
            
            int contentLength = this.getContentLength();
            this.requestBody = new ByteArrayOutputStream(contentLength);
            byte [] buffer = new byte[8096];
            int bytesToRead = contentLength, bytesRead = 0;
            while( contentLength > 0 ){
            	
                bytesToRead=Math.min(contentLength,buffer.length);
                if( (bytesRead = in.read(buffer, 0, bytesToRead)) > -1 ){
                    this.requestBody.write(buffer,0,bytesRead);
                    contentLength-=bytesRead;
                }
                else
                    contentLength = 0;
            }
            this.requestBody.flush();
        }
    }
    
    /*
     * return the last modified header value, or DateTime.Now() if doesn't exist.
     */
    public int getContentLength() {
        if( this.headers.containsKey("Content-Length") )
            return Integer.parseInt(
                    (headers.get("Content-Length")).getFirst().toString());
        else
            return 0;
    }
   
    /*
     * adding "If-Modified-Since" header with lastModified value (for cache check)
     */
    public void setIfModifiedSince(String lastModified){
        LinkedList<String> dateList=new LinkedList<String>();
        dateList.add(lastModified);
        headers.put("If-Modified-Since", dateList);
    }
            
    /*
     * return true if connection is persistent
     */
    public boolean getKeepAlive (){
        LinkedList<String> values = null;
        String key = null;
        // Default is true for http 1.1
        boolean keepAlive = version == null ? false : version.equalsIgnoreCase("HTTP/1.1");
        /* 
         * HTTP/1.1 defines the "close" connection option for the sender 
         * to signal that the connection will be closed after completion of the response
         */ 
        key = "Connection";
        if( headers.containsKey(key) )
        {
            if( (values = headers.get(key)) != null )
            {
                if( values.contains("close") )
                    return false;
            }
        }
        key = "Proxy-Connection";
        if( headers.containsKey(key) )
        {
            if( (values = headers.get(key)) != null )
            {
                if( values.contains("close") )
                    return false;
            }
        }
        return keepAlive;
    }
    
    /*
     * return the Http version
     */
    public String getVersion(){
        return version;
    }
    /*
     * return the request method
     */
    public String getMethod(){
        return method;
    }
    /*
     * return the url as URL class
     */
    public URL  getUrl() {
        return url;
    }
    
    /*
     * Gets the port used by this request or default port for the used protocol if not defined. The value is conformed with the rfc.
     */
    public int getPort()
    {
        if( url == null )
            return -1;
        int port = url.getPort();
        // If the port is not specified, the default port for the protocol is used instead.
        if( port == -1 )
            port = url.getDefaultPort();
        // return
        return port;
    }
    
    /*
     * Gets the host used by this request. The value is conformed with the rfc.
     */
    public String getHost()
    {
        LinkedList<String> values = null;
        String key = null;

        String host = "";
        key = "Host";
        if( headers.containsKey(key) )
        {
            if( (values = headers.get(key)) != null );
                host = values.getFirst();
        }
        else
            host = url.getHost();
        return (host!=null ? host.trim() : "");
    }
    
    /*
     * return the headers HashMap as unmodifiable map
     */ 
    public Map<String,LinkedList<String>> getHeaders() {
        return Collections.unmodifiableMap(headers);
    }
    
    public void write(OutputStream out) throws IOException
    {
        synchronized( out ) {
            ServerOutputStream dos = new ServerOutputStream(out);
            // Generate Headers
            StringBuffer headers = new StringBuffer();
            headers.append(this.requestLine);
            headers.append("\r\n");
            // Write headers
            Iterator<String> keys = this.getHeaders().keySet().iterator();
            while( keys.hasNext() )
            {
                String key = (String)keys.next();
                List<String> value = this.getHeaders().get(key);
                headers.append(
                    MessageFormat.format("{0}: ", 
                        new Object[] { key }));

                Iterator<String> values = value.iterator();
                while( values.hasNext() )
                {
                    headers.append(
                        MessageFormat.format("{0}", 
                            new Object[] { values.next() }));
                    if( values.hasNext() )
                         headers.append(", ");
                }
                // Add headers CRLF
                headers.append("\r\n");
            }
            // Another CRLF to indicate that we are done with the headers.
            headers.append("\r\n");
            // Output the headers
            dos.writeBytes(headers.toString());
            dos.flush();
            // We are done with the headers, start forwarding the request body, byte by byte.
            this.requestBody.writeTo(dos);
            dos.flush();
        }
    }
}
    